package net.uman.framework.modules.objectpool;

import java.lang.reflect.InvocationTargetException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import net.uman.entity.IdEntity;
import net.uman.framework.modules.orm.Page;
import net.uman.framework.modules.utils.EncodeUtils;

import org.apache.log4j.BasicConfigurator;

import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;

public class ObjectPool {
	private boolean enable;

	private ObjectPoolLogger logger = new ObjectPoolLogger();  
	
	public final static int ONE_MINUTE = 60;  // 1m
	public final static int ONE_HOUR = 60 * 60;  // 1h
	public final static int ONE_DAY = 60 * 60 * 24;  // 1day
	public final static int DEFAULT_LIFE = 60 * 60;  // 1h

	private PoolConfig config = new PoolConfig();
	private MemCachedClient mcc = null;
	private String[] servers = {"localhost:11211"};
	
	private synchronized MemCachedClient _init() {
		return _init(servers);
	}
	
	private synchronized static MemCachedClient _init(String[] servers) {
		// memcached should be running on port 11211 but NOT on 11212
		BasicConfigurator.configure();
		
		SockIOPool pool = SockIOPool.getInstance();
		pool.setServers( servers );
		pool.setFailover( true );
		pool.setInitConn( 10 ); 
		pool.setMinConn( 5 );
		pool.setMaxConn( 250 );
		pool.setMaxIdle( 1000 * 60 * 60 * 6 ); 
		
		// set the sleep for the maint thread
		// it will wake up every x seconds and
		// maintain the pool size
		pool.setMaintSleep( 30 ); 
		// Tcp的规则就是在发送一个包之前，本地机器会等待远程主机
		// 对上一次发送的包的确认信息到来；这个方法就可以关闭套接字的缓存，
		// 以至这个包准备好了就发； 
		pool.setNagle( false );
		
		//连接建立后对超时的控制
		pool.setSocketTO( 3000 );
		//连接建立时对超时的控制
		pool.setSocketConnectTO( 0 ); 
		
		pool.setAliveCheck( true );
		pool.initialize();

		MemCachedClient mcc = new MemCachedClient();

		// turn off most memcached client logging:
		//Logger.getLogger( MemCachedClient.class.getName() ).setLevel( com.schooner.MemCached.Logger. );
		return mcc;
	}
	
	public void setLogFile(String logFile) {
		this.logger.setLogFile(logFile);
	}
	
	public void setEnable(boolean enable) {
		this.enable = enable;
	}

	public boolean isEnable() {
		return enable;
	}

	public void setLogEnable(boolean logEnable) {
		this.logger.setEnable(logEnable);
	}

	public void setServers(String servers) {
		this.servers = servers.split(",");
	}
	
	public void init() {
		mcc = _init();
	}
	
	public void init(String[] servers) {
		mcc = _init(servers);
	}

	/**
	 * @return true if success
	 */
	public boolean set(IdEntity obj) {
		return set(KeyGenerator.getKey(obj), obj);
	}
	
	/**
	 * @return true if success
	 */
	public boolean set(String key, Object value) {
		if (!enable) {
			return true;
		}
		
		if (mcc == null) {
			mcc = _init();
		}
		
//		value = convert(value);
		return mcc.set(encodeKey(key), value);
	}
	
	private boolean _set(String key, Object value) {
		if (!enable) {
			return true;
		}
		
		if (mcc == null) {
			mcc = _init();
		}
		
		return mcc.set(encodeKey(key), value);
	}
	
	/*
	 * @return true if success
	 */
	public boolean set(IdEntity obj, int lifeSeconds) {
		return set(KeyGenerator.getKey(obj), obj, lifeSeconds);
	}
	
	/**
	 * @param lifeSeconds the data will be discard after the given seconds.
	 * @return true if success  
	 */
	public boolean set(String key, Object value, int lifeSeconds) {
		if (!enable) {
			return true;
		}

		if (mcc == null) {
			mcc = _init();
		}

//		value = convert(value);
		return mcc.set(encodeKey(key), value, new Date(lifeSeconds * 1000));
	}
	
	public IdEntity get(Class model, Long id) {
		return (IdEntity)get(KeyGenerator.getKey(model, id));
	}
	
	public IdEntity[] get(Class model, String[] ids) {
		String[] keys = new String[ids.length];
		for (int i = 0 ; i < ids.length ; i++) {
			keys[i] = KeyGenerator.getKey(model, new Long(ids[i]));
		}
		
		Object[] values = get(keys);
		IdEntity[] ret = new IdEntity[values.length];
		for (int i = 0 ; i < values.length ; i++) {
			ret[i] = (IdEntity)values[i];
		}
		return ret;
	}
	
	public Object[] get(String[] keys) {
		if (!enable) {
			return null;
		}
		
		if (mcc == null) {
			mcc = _init();
		}
		
		for (int i = 0 ; i < keys.length ; i++) {
			keys[i] = encodeKey(keys[i]);
		}
		Map<String, Object> values = mcc.getMulti(keys);
		Object[] ret = new Object[keys.length];
		for (int i = 0 ; i < keys.length ; i++) {
			ret[i] = values.get(keys[i]);
			if (ret[i] != null) {
//				ret[i] = convert(ret[i]);
			}
		}
		
		return ret;		
	}
	
	public Object get(String key) {
		if (!enable) {
			return null;
		}
		
		if (mcc == null) {
			mcc = _init();
		}
		
		Object value = mcc.get(encodeKey(key));
		if (value != null) {
//			value = convert(value);
			logger.writeHitLog(key);
//			System.out.println("Object pool hit: " + key);
		} else {
			logger.writeMissedLog(key);
//			System.out.println("Object pool missed: " + key);
		}
		return value;
	}

	public void remove(IdEntity obj) {
		remove(KeyGenerator.getKey(obj));
	}

	public void remove(Class model, String id) {
		remove(KeyGenerator.getKey(model, new Long(id)));
	}
	
	public void remove(String key) {
		if (!enable) {
			return;
		}
		
		if (mcc == null) {
			mcc = _init();
		}
		
		mcc.delete(encodeKey(key));
	}

	public boolean isCachable(Class cls) {
		if (cls.getName().contains("$$")) {
			cls = cls.getSuperclass();
		}
		return config.isCachable(cls);
	}
	
	public int getLife(Class cls) {
		return config.getLife(cls);
	}
	
	private Object convert(Object value) {
		if (value instanceof Page) {
			value = convert((Page)value);
		} else if (value instanceof List) {
			value = convert((List)value);
		} else if (value instanceof IdEntity) {
			value = convert((IdEntity)value);
		} else if (value instanceof BeanObject) {
			value = convert((BeanObject)value);			
		}
		return value;
	}
	
	private Page convert(Page page) {
		Page _page = new Page();
		_page.setAutoCount(page.isAutoCount());
		_page.setOrder(page.getOrder());
		_page.setOrderBy(page.getOrderBy());
		_page.setPageNo(page.getPageNo());
		_page.setPageSize(page.getPageSize());
		_page.setTotalCount(page.getTotalCount());
		
		List objs = page.getResult();
		objs = convert(objs);
		_page.setResult(objs);
		return _page;
	}

	private List convert(List objs) {
		if (objs == null || objs.size() ==0) {
			return null;
		}

		List _objs = new ArrayList();
		for (int i = 0 ; i < objs.size() ; i++) {
			Object o = objs.get(i);
			if (o instanceof IdEntity) {
				o = convert((IdEntity)o);
			} else if (o instanceof BeanObject) {
				o = new MapAction().getModel((BeanObject)o);
			} else {
				throw new RuntimeException("Unknown object: "+o.getClass());
			}
			_objs.add(o);
		}
		
		return _objs;
	}
	
	private BeanObject convert(IdEntity o) {
		BeanObject bean = null;
		try {
			bean = new MapAction().getBeanObject(o);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
		
		_set(o.getClass().getName()+"@"+o.getId(), bean);
		
		return bean;
	}
	
	private IdEntity convert(BeanObject bean) {
		IdEntity o = new MapAction().getModel(bean);
		return o;
	}
	
	// key maybe include some char invalid for memcache, so we need to encode it 
	private String encodeKey(String key) {
		try {
			java.security.MessageDigest md5 = java.security.MessageDigest.getInstance( "MD5" );
			md5.update(key.getBytes());
		    byte tmp[] = md5.digest(); 
		    key = EncodeUtils.base64Encode(tmp);
			return key;
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return key;
		}
	}
	
	public static void main(String[] args) {
		long t1 = new Date().getTime();
		ObjectPool pool = new ObjectPool();
		pool.setEnable(true);
//		for (int i = 0 ; i < 1000000 ; i++) {
//			pool.set(""+i, ""+i);
//		}
//		
//		long t2 = new Date().getTime();
//		
//		System.out.println("set time:"+(t2-t1));
//		
//		for (int i = 0 ; i < 1000000 ; i++) {
//			pool.get(""+i);
//		}
//		long t3 = new Date().getTime();
//		
//		System.out.println("set time:"+(t3-t2));
	}
}
